fix(window): persist and restore window size and position across restarts - #16
Conversation
…arts Only the window position was saved (and only at exit); the constructor always reset to 1080x720, so every restart lost the user's size and shifted the window (issue #11). - Add LastWindowWidth/LastWindowHeight config keys, round-tripped with the existing position keys. - Restore saved size and position at startup, but only when the saved caption strip still intersects an attached display's working area; a changed monitor layout falls back to default placement instead of resurrecting the window off-screen. - Save normal-mode bounds at exit: from minimal UI use the pre-minimal restore bounds, from maximized use RestoreBounds, otherwise live geometry. First-run minimal closes keep any previously saved size. - Exiting minimal UI with no in-session restore bounds (started directly into minimal mode) now also restores persisted bounds. Regression tests cover config round-trip, missing-key defaults, and on/off-screen visibility validation. Fixes #11
📝 WalkthroughWalkthroughWindow geometry persistence now includes size and position, validates minimum dimensions and display visibility during restoration, handles minimal-to-normal transitions, and selects validated bounds during shutdown. Tests cover configuration round trips, missing size defaults, DPI conversion, and screen visibility. ChangesWindow bounds persistence
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant PageSwitcher
participant RatConfig
participant WindowsGameDisplayService
User->>PageSwitcher: launch or return to normal UI
PageSwitcher->>RatConfig: read saved window geometry
PageSwitcher->>WindowsGameDisplayService: read display DPI and working areas
WindowsGameDisplayService-->>PageSwitcher: return logical display bounds
PageSwitcher->>PageSwitcher: validate and apply visible bounds
User->>PageSwitcher: close application
PageSwitcher->>PageSwitcher: select and validate persistable bounds
PageSwitcher->>RatConfig: save window geometry
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
|
Addressed the fresh Kilo review finding in eb4f444: persistence now receives the minimal-mode restore rectangle and WPF RestoreBounds as separate inputs, so minimized/maximized exits use the real state restore geometry even when minimal UI was never entered. The minimized regression test now explicitly supplies an empty minimal restore rectangle and a valid WPF state restore rectangle. Release build and full tests remain clean (216 passed, 5 fixture-dependent skips). |
Root cause
Only
LastWindowPositionX/Ywere persisted, and only at exit.PageSwitcher's constructor always calledResetWindowSize()(1080×720), so every restart discarded the user's size and re-anchored the position to a stale coordinate pair — the window shifted exactly as described in #11.Fix
LastWindowWidth/LastWindowHeightconfig keys, round-tripped alongside the existing position keys (backwards compatible; missing keys stay unset).IsVisibleOnAnyScreen). If the monitor layout changed since last run, the window falls back to default placement instead of appearing off-screen.RestoreBoundsValidation
WindowBoundsPersistenceTests: config round-trip, missing-key defaults, off-screen rejection, on-screen acceptance (209 passed total).Fixes #11
Summary by cubic
Persist and restore window size and position across restarts. Adds DPI‑correct on‑screen checks so the window won’t resurrect off‑screen after monitor or scale changes.
LastWindowWidth/LastWindowHeightconfig keys; backward‑compatible when missing.Screen.WorkingAreafrom physical pixels to WPF logical units using per‑monitor DPI fromWindowsGameDisplayServiceand ignore unreliable DPI reads.RestoreBounds; otherwise use live geometry. Skip saving minimal‑UI coordinates when no normal bounds exist; save once during shutdown.TryGetRestorableBounds,TryGetPersistableBounds, physical→logical working‑area conversion, and high‑DPI visibility validation.Written for commit 84993d6. Summary will update on new commits.
Greptile Summary
This PR persists and restores window size (new
LastWindowWidth/LastWindowHeightconfig keys) alongside the existing position keys, and adds monitor-layout validation so the window falls back to default placement when the saved position lands off all currently-attached displays.RestoreWindowBounds/PersistWindowBoundsreplace the previous inline save/restore, correctly handling the maximized, minimal-UI, and first-run-minimal-mode edge cases.IsVisibleOnAnyScreenvalidates the caption strip againstScreen.AllScreensworking areas before restoring position; however, the app runsPerMonitorV2DPI awareness (set inProgram.Main) soScreen.WorkingAreareturns physical pixels while WPF window coordinates are device-independent units — these diverge at non-100% DPI and can cause off-screen positions to pass the check.Confidence Score: 3/5
The core persistence logic and edge-case handling are sound, but the off-screen guard compares WPF logical coordinates against physical-pixel screen bounds in a PerMonitorV2 process, which means the guard can silently accept off-screen positions on high-DPI machines.
The coordinate mismatch in IsVisibleOnAnyScreen directly undermines the fix for issue #11: on any machine running at a non-100% DPI scale, a window that was on a now-disconnected monitor can still pass the visibility check and be restored off-screen. The config round-trip and the minimal-UI/maximized branching logic are well-structured and backwards-compatibility is clean, but the DPI bug affects the one guard added specifically to prevent the reported problem.
Files Needing Attention: src/App/PageSwitcher.xaml.cs — specifically IsVisibleOnAnyScreen and the fallback position-save path in PersistWindowBounds; tests/RatScanner.Tests/WindowBoundsPersistenceTests.cs — the on-screen acceptance test.
Important Files Changed
Sequence Diagram
sequenceDiagram participant Config as RatConfig participant PS as PageSwitcher participant Screen as Screen.AllScreens note over PS: Startup PS->>Config: LoadConfig() PS->>PS: ResetWindowSize() (1080x720 defaults) PS->>Config: Read LastWindowWidth/Height/X/Y PS->>PS: RestoreWindowBounds() PS->>Screen: IsVisibleOnAnyScreen(left, top, width, height) Screen-->>PS: true / false alt position on-screen PS->>PS: "Left=saved, Top=saved, Width=saved, Height=saved" else position off-screen PS->>PS: keep default placement end note over PS: Enter Minimal UI PS->>PS: "_restoreBounds = RestoreBounds" PS->>PS: ShowMinimalUI() note over PS: Exit Minimal UI (ShowUI) alt _restoreBounds non-empty PS->>PS: restore from _restoreBounds else started in minimal mode (no in-session bounds) PS->>PS: ResetWindowSize() + RestoreWindowBounds() end note over PS: Exit / Close PS->>PS: PersistWindowBounds() alt closing from minimal UI and _restoreBounds empty PS->>Config: "LastWindowPositionX/Y = minimal UI Left/Top" else normal geometry PS->>Config: "LastWindowPositionX/Y/Width/Height = bounds" end PS->>Config: SaveConfig()Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix(window): persist and restore window ..." | Re-trigger Greptile